#pragma once

#include <iostream>
#include <sys/select.h>  // select相关定义
#include <sys/time.h>    // 时间相关结构
#include "Socket.hpp"    // 套接字类

using namespace std;

// 默认配置
static const uint16_t defaultport = 8888;            // 默认端口号
static const int fd_num_max = (sizeof(fd_set) * 8);  // 最大文件描述符数量
int defaultfd = -1;                                  // 默认文件描述符值

// Select服务器类
class SelectServer
{
public:
    // 构造函数，初始化服务器端口和文件描述符数组
    SelectServer(uint16_t port = defaultport) : _port(port)
    {
        // 初始化文件描述符数组
        for (int i = 0; i < fd_num_max; i++)
        {
            fd_array[i] = defaultfd;
        }
    }

    // 初始化服务器
    bool Init()
    {
        // 第一步：创建套接字
        // 调用Sock类的Socket方法创建TCP套接字
        // 内部会调用socket()系统调用，创建一个IPv4的流式套接字
        _listensock.Socket();

        // 第二步：绑定端口
        // 调用Sock类的Bind方法，将套接字与指定端口绑定
        // 将服务器端口(_port)与创建的套接字关联
        // 使得客户端可以通过这个端口连接到服务器
        _listensock.Bind(_port);

        // 第三步：开启监听
        // 调用Sock类的Listen方法，将套接字设置为监听状态
        // 使得服务器可以接收客户端的连接请求
        // 在Socket.hpp中定义了默认的监听队列长度backlog = 10
        _listensock.Listen();

        // 初始化成功，返回true
        // 如果上述任何步骤失败，会在各自的函数中处理错误
        return true;
    }

    // 处理新的客户端连接请求
    void Accepter() {
        std::string clientip;      // 保存客户端的IP地址,如"192.168.1.100"
        uint16_t clientport = 0;   // 保存客户端的端口号,如8080,用16位无符号整型表示(0-65535)
        
        // 接受新连接,获取客户端IP和端口信息
        int sock = _listensock.Accept(&clientip, &clientport);
        if (sock < 0) return;    // 接受失败则返回
        // 打印新连接的信息到日志
        lg(Info,                      // Info级别的日志
            "accept success, %s: %d, sock fd: %d",   // 格式化字符串
            clientip.c_str(),         // 客户端IP, c_str()转换string为char*
            clientport,               // 客户端端口号
            sock);                    // 新分配的socket文件描述符

        // 在fd_array中查找一个空闲位置
        int pos = 1;    // 从1开始,0通常是监听socket
        for (; pos < fd_num_max; pos++) {
            if (fd_array[pos] != defaultfd)  // defaultfd通常是-1
                continue;
            else
                break;  // 找到空闲位置
        }

        // 判断是否达到最大连接数
        if (pos == fd_num_max) {  
            // 连接数已满,关闭新连接
            lg(Warning, "server is full, close %d now!", sock);
            close(sock);
        }
        else {
            // 将新连接的socket加入到fd_array
            fd_array[pos] = sock;
            PrintFd();    // 打印当前所有连接
        }
    }

    // 处理客户端发送来的数据
    void Recver(int fd,     // 就绪的文件描述符
        int pos)     // 该描述符在fd_array中的位置
    {
        char buffer[1024];  // 缓冲区存储读取的数据

        // 读取数据
        ssize_t n = read(fd, buffer, sizeof(buffer) - 1);

        if (n > 0) {  // 读取成功
        buffer[n] = 0;  // 添加字符串结束符
        cout << "get a messge: " << buffer << endl;
        }
        else if (n == 0) {  // 返回0表示对端关闭连接
        lg(Info, "client quit, me too, close fd is : %d", fd);
        close(fd);                    // 关闭socket
        fd_array[pos] = defaultfd;    // 释放数组位置
        }
        else {  // 返回-1表示出错
        lg(Warning, "recv error: fd is : %d", fd);
        close(fd);                    // 关闭出错的socket
        fd_array[pos] = defaultfd;    // 释放数组位置
        }
    }

    // 事件分发器 - 处理所有就绪的文件描述符
    void Dispatcher(fd_set &rfds) {
        // 遍历所有文件描述符
        for (int i = 0; i < fd_num_max; i++) {
            int fd = fd_array[i];
            if (fd == defaultfd)    // 跳过无效描述符
                continue;

            // 检查这个描述符是否在就绪集合中
            if (FD_ISSET(fd, &rfds)) {
                if (fd == _listensock.Fd()) {  
                    // 如果是监听socket就绪,表示有新连接
                    Accepter();     // 处理新连接
                }
                else {  
                    // 如果是普通socket就绪,表示有数据可读
                    Recver(fd, i);  // 读取数据
                }
            }
        }
    }

    // 启动服务器
    void Start()
    {
        int listensock = _listensock.Fd();
        fd_array[0] = listensock;  // 将监听套接字放在数组首位
        
        for (;;)
        {
            fd_set rfds; // 声明可读文件描述符集合
            FD_ZERO(&rfds);  // 清空文件描述符集合

            // 寻找最大文件描述符并将所有活跃描述符加入监听集合
            int maxfd = fd_array[0];  // 初始化最大值为第一个文件描述符
            for (int i = 0; i < fd_num_max; i++) {
                if (fd_array[i] == defaultfd)   // 跳过未使用的槽位
                    continue;
                FD_SET(fd_array[i], &rfds);     // 加入监听集合
                if (maxfd < fd_array[i]) {      // 更新最大文件描述符
                    maxfd = fd_array[i];
                    lg(Info, "max fd update, max fd is: %d", maxfd);
                }
            }

            // 使用select进行IO多路复用等待
            int n = select(maxfd + 1,    // 最大文件描述符+1
                &rfds,         // 监控可读事件的描述符集合
                nullptr,       // 不监控可写事件
                nullptr,       // 不监控异常事件
                nullptr);      // 阻塞等待,不设置超时

            switch (n) {
            case 0:   // select返回0表示超时
            cout << "time out" << endl;
            break;

            case -1:  // 返回-1表示出错
            cerr << "select error" << endl;
            break;

            default:  // 返回值>0表示就绪的文件描述符数量
            cout << "get a new link!!!!!" << endl;
            Dispatcher(rfds);    // 处理就绪的事件
            break;
            }
        }
    }

    // 打印当前活跃的文件描述符
    void PrintFd()
    {
        cout << "online fd list: ";
        for (int i = 0; i < fd_num_max; i++)
        {
            if (fd_array[i] == defaultfd)
                continue;
            cout << fd_array[i] << " ";
        }
        cout << endl;
    }

    // 析构函数
    ~SelectServer()
    {
        _listensock.Close();
    }

private:
    Sock _listensock;           // 监听套接字
    uint16_t _port;             // 服务器端口
    int fd_array[fd_num_max];   // 文件描述符数组
};